package ar.edu.itba.it.cg.grupo01.impl.shape.shader;

import java.awt.Color;
import java.util.List;

import javax.vecmath.Point2f;
import javax.vecmath.Point3f;
import javax.vecmath.Vector3f;

import ar.edu.itba.it.cg.grupo01.api.Intersection;
import ar.edu.itba.it.cg.grupo01.api.scene.Scene;
import ar.edu.itba.it.cg.grupo01.api.shape.Shape;
import ar.edu.itba.it.cg.grupo01.api.shape.light.Light;
import ar.edu.itba.it.cg.grupo01.impl.RayImpl;
import ar.edu.itba.it.cg.grupo01.impl.RayTraceInformation;
import ar.edu.itba.it.cg.grupo01.impl.RayTracer;
import ar.edu.itba.it.cg.grupo01.impl.RayTracer.ColorHolder;
import ar.edu.itba.it.cg.grupo01.impl.color.Colors;
import ar.edu.itba.it.cg.grupo01.impl.scene.optimization.Octree;
import ar.edu.itba.it.cg.grupo01.impl.shape.Plane;
import ar.edu.itba.it.cg.grupo01.impl.shape.Triangle;

public abstract class PerlinShader extends Shader {

	protected Color noNoise;
	protected Color fullNoise;
	protected int times;
	protected float persistence;

	public PerlinShader(Color noNoise, Color fullNoise, int times, float persistence) {
		this.noNoise = noNoise;
		this.fullNoise = fullNoise;
		this.times = times;
		this.persistence = persistence;
	}

	public PerlinShader(Color noNoise, Color fullNoise) {
		this.noNoise = noNoise;
		this.fullNoise = fullNoise;
	}

	protected final float turbulence(Point2f p) {
		float t = 0.0f;
		Point3f dummyZ = new Point3f(p.x,p.y,0);
		for (int i = 0; i < times; i++) {
			float f = (float) Math.pow(2.0,i);
			
			t += getTurbulenceFunction(dummyZ,PerlinNoise.noise2(f * p.x, f * p.y),f,times)* Math.pow(persistence, i);
		}
		return t;
	}

	protected final float turbulence(Point3f p) {
		float t = 0.0f;

		for (int i = 0; i < times; i++) {
			float f = (float) Math.pow(i, times);
			t += getTurbulenceFunction(p, PerlinNoise.noise3(f * p.x, f * p.y, f * p.z),f,times) * Math.pow(persistence, i);
		}
		return t;
	}
	
	protected abstract float getNoise(Shape shape, Point3f intersectionPoint);
	
	protected abstract float getTurbulenceFunction(Point3f p, float noise, float frequency, int times);

	@Override
	public Color getShadeColor(Shape shape, Point3f intersectionPoint) {
		float noise = getNoise(shape, intersectionPoint);
		
		return Colors.getPerlin(noise, noNoise, fullNoise);
	}

	@Override
	public void shade(RayTraceInformation info) {
		RayTracer tracer = info.getTracer();
		Point3f intersectionPoint = info.getIntersectionPoint();
		Shape shape = info.getShape();
		Scene scene = info.getScene();
		ColorHolder color = info.getColor();
		Vector3f normal = info.getNormal();

		if (info.getIntersection().getType().equals(Intersection.Type.IN)) {
			normal.negate();
		}

		for (Light light : scene.getLights()) {

			List<Vector3f> dirs = light.getLigthDirs(intersectionPoint);
			float attenuation = light.getAttenuation(intersectionPoint);
			int hits = 0;

			Octree octree = info.getOctree();
			List<Plane> planes = scene.getPlanes();
			for (Vector3f lightDir : dirs) {
				if (tracer.hitsLight(shape, light, new RayImpl(
						intersectionPoint, lightDir), octree, planes)) {
					hits++;
				}
			}

			if (hits > 0) {
				float dot = dirs.get(0).dot(normal);

				if (shape instanceof Triangle && dot < 0) {
					dot = -dot;
				}

				if (dot > 0.0) {
					ColorHolder attenuatedLightColor = new ColorHolder(Colors
							.scale(light.getColor(), attenuation));

					if (hits != dirs.size()) {
						attenuatedLightColor.mul(hits);
						attenuatedLightColor.mul(1.0f / dirs.size());
					}

					Color perlinColor = Colors
							.mulTo(getShadeColor(shape, intersectionPoint),
									attenuatedLightColor.getColor());

					color.madd(dot, perlinColor);

				}
			}
		}
	}
	
}
